import numpy as np
from sklearn.random_projection import GaussianRandomProjection
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']  # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

# 1. 数据生成
np.random.seed(42)
high_dim = 100    # 高维空间维度
low_dim = 10      # 降维后的目标维度
num_points = 1000  # 数据点数量

# 生成高维数据点
data_high_dim = np.random.rand(num_points, high_dim)

# 2. 随机投影矩阵生成（修正：添加缩放因子）
# 根据Johnson-Lindenstrauss引理，需要缩放投影矩阵
scaling_factor = np.sqrt(low_dim)
random_projection_matrix = np.random.normal(0, 1/scaling_factor, size=(high_dim, low_dim))

# 3. 投影到低维空间
data_low_dim = np.dot(data_high_dim, random_projection_matrix)

# 4. 原始距离与投影距离对比
# 选取多个点对计算距离，而不仅仅是两个点
num_pairs = 100
original_distances = []
projected_distances = []

for _ in range(num_pairs):
    # 随机选择两个不同的点
    i, j = np.random.choice(num_points, 2, replace=False)
    
    # 高维空间中的欧氏距离
    orig_dist = np.linalg.norm(data_high_dim[i] - data_high_dim[j])
    original_distances.append(orig_dist)
    
    # 低维空间中的欧氏距离
    proj_dist = np.linalg.norm(data_low_dim[i] - data_low_dim[j])
    projected_distances.append(proj_dist)

# 5. 使用sklearn实现随机投影
transformer = GaussianRandomProjection(n_components=low_dim, random_state=42)
data_low_dim_sklearn = transformer.fit_transform(data_high_dim)

# 计算sklearn降维后的距离
projected_distances_sklearn = []
for _ in range(num_pairs):
    i, j = np.random.choice(num_points, 2, replace=False)
    proj_dist_sklearn = np.linalg.norm(data_low_dim_sklearn[i] - data_low_dim_sklearn[j])
    projected_distances_sklearn.append(proj_dist_sklearn)

# 6. 输出结果和距离保持性分析
print("单个点对的距离比较:")
i, j = 0, 1
orig_dist_single = np.linalg.norm(data_high_dim[i] - data_high_dim[j])
proj_dist_single = np.linalg.norm(data_low_dim[i] - data_low_dim[j])
proj_dist_single_sklearn = np.linalg.norm(data_low_dim_sklearn[i] - data_low_dim_sklearn[j])

print(f"原始高维数据点之间的距离: {orig_dist_single:.6f}")
print(f"随机投影降维后数据点之间的距离 (自定义实现): {proj_dist_single:.6f}")
print(f"随机投影降维后数据点之间的距离 (sklearn实现): {proj_dist_single_sklearn:.6f}")

print("\n距离保持性分析 (基于100个随机点对):")
# 计算距离保持的比率
ratios_custom = [p/o for o, p in zip(original_distances, projected_distances)]
ratios_sklearn = [p/o for o, p in zip(original_distances, projected_distances_sklearn)]

print(f"自定义实现 - 距离比率均值: {np.mean(ratios_custom):.4f}, 标准差: {np.std(ratios_custom):.4f}")
print(f"Sklearn实现 - 距离比率均值: {np.mean(ratios_sklearn):.4f}, 标准差: {np.std(ratios_sklearn):.4f}")

# 7. 可视化距离保持情况
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.scatter(original_distances, projected_distances, alpha=0.7)
plt.plot([0, max(original_distances)], [0, max(original_distances)], 'r--')
plt.xlabel('原始距离')
plt.ylabel('投影后距离')
plt.title('自定义实现 - 距离保持')
plt.grid(True)

plt.subplot(1, 2, 2)
plt.scatter(original_distances, projected_distances_sklearn, alpha=0.7)
plt.plot([0, max(original_distances)], [0, max(original_distances)], 'r--')
plt.xlabel('原始距离')
plt.ylabel('投影后距离')
plt.title('Sklearn实现 - 距离保持')
plt.grid(True)

plt.tight_layout()
plt.show()

print("\n随机投影后的部分数据点:")
print(data_low_dim[:5])  # 显示降维后的前5个数据点